Skip to content

feat(core): Phase 1 — iOS element regions via maestro hierarchy (cross-platform parity, behind env switch)#2202

Draft
Sriram567 wants to merge 4 commits intofeat/maestro-multipart-uploadfrom
feat/ios-element-regions-maestro-hierarchy
Draft

feat(core): Phase 1 — iOS element regions via maestro hierarchy (cross-platform parity, behind env switch)#2202
Sriram567 wants to merge 4 commits intofeat/maestro-multipart-uploadfrom
feat/ios-element-regions-maestro-hierarchy

Conversation

@Sriram567
Copy link
Copy Markdown

Summary

Phase 1 of the strategic pivot from WDA-direct (#2201) to a cross-platform
maestro hierarchy resolver. Default behavior unchanged — the new path
is gated behind a PERCY_IOS_RESOLVER env switch that defaults to
wda-direct. Customers see no production change until Phase 0.5 empirical
probe passes and Phase 4 flips the default.

Reference plan: percy-maestro/docs/plans/2026-04-27-001-feat-ios-element-regions-maestro-hierarchy-plan.md
(local-only repo; not pushed to GitHub yet).

Why pivot

The 2026-04-22 brainstorm rejected maestro hierarchy for iOS as
"session-exclusive" without empirical testing. The 2026-04-27 spike (run
on BS iOS host 52) proved otherwise:

  • Maestro CLI is present in /nix/store on iOS BS hosts (multiple
    versions, all wrapping Maestro 1.39.10). Java 17 (Zulu) is available.
  • realmobile already drives maestro --device=<udid> --driver-host-port <P>
    with P = wda_port + 2700 deterministically (see
    realmobile/maestro_session.rb:831). Same gRPC-over-iproxy transport an
    external maestro hierarchy invocation would use.
  • iOS has the equivalent device-side runner Android has —
    dev.mobile.maestro-driver-iosUITests.xctrunner-{2.1,2.2,3,4}.app is
    precached on the host, structurally analogous to Android's
    dev.mobile.maestro app.

The brainstorm's "session-exclusive" claim conflated maestro hierarchy
with maestro studio. Studio is exclusive; hierarchy on Android
demonstrably is not (production usage in maestro-hierarchy.js for
years).

Customer benefit: one resolver module across both platforms; same yaml
shape; same docs; one mental model. Engineering benefit: drops the
realmobile wda-meta.json contract (~250 lines of cross-tenant
filesystem hardening + 8 security acceptance tests + cross-team
coordination) in favor of two env vars realmobile already knows.

What lands here (Phase 1, gated by env switch)

4 commits, in order:

  • 9e2f3815 Unit 1 — Rename adb-hierarchy.jsmaestro-hierarchy.js
    • 5-line compat shim. Pure rename; no behavior change.
  • 403d89fc Unit 2a — Platform-dispatch scaffolding in dump({ platform }).
    iOS branch reads PERCY_IOS_DEVICE_UDID + PERCY_IOS_DRIVER_HOST_PORT
    env vars (realmobile-injected; the wda_port + 2700 formula stays in
    realmobile) and currently returns { kind: 'unavailable', reason: 'not-implemented' } as a FIXME-PHASE-0.5 stub. The real iOS
    attribute-mapping logic lands in Unit 2b (separate PR) post Phase 0.5.
    Also lands R1 vocabulary parity: Android's flattenNodes and
    flattenMaestroNodes now surface resource-id value under both
    resource-id AND id canonical keys. Customers writing
    {element: {id: "X"}} work on Android too.
  • 1b98ece6 Unit 3api.js dispatch behind PERCY_IOS_RESOLVER
    env switch. Default off → existing WDA-direct iOS path. Switch on →
    cross-platform Android-style lazy dump + per-region firstMatch.
  • 616cdd56 Unit 4 — Cross-platform parity test
    (maestro-hierarchy.parity.test.js) + bug fix for the XML-path id
    alias (smoke-test caught a divergence between the JSON path and XML
    path).

Phase status

  • Phase 1 (this PR) — additive resolver code, env switch off by
    default. ✅ Done.
  • Phase 2 — docs + check-in the probe artifact. Pending.
  • Phase 0.5 — empirical probe re-run on a healthy BS iOS Maestro
    session. BLOCKED on a current BS Maestro infra outage (5 builds in
    a row failed at the MAESTRO test started step with no further detail
    — see percy-maestro/docs/experiments/2026-04-27-maestro-hierarchy-spike/findings.md).
  • Phase 4 — deletes WDA-direct (feat(core): iOS element regions via WDA-direct (Plan A — gated for deletion in Phase 4) #2201) once Phase 0.5 PASSes; gated
    on Unit 10a in realmobile (env-var exports + denylist filter for
    cross-tenant override defense).

Decision tree if/when Phase 0.5 runs

Testing

Smoke-tested via direct node import — all whitelist exports correct,
iOS env-missing path returns 'env-missing', env-set returns
'not-implemented' stub, R1 alias parity holds through both XML
(uiautomator) and JSON (maestro CLI) code paths. Full @percy/core test
suite has 27 pre-existing Chromium-installer failures unrelated to this
PR.

New tests:

  • test/unit/maestro-hierarchy.test.js — extends existing Android tests
    with R1 id alias coverage (3 tests) and iOS branch coverage (5 tests).
  • test/unit/maestro-hierarchy.parity.test.js — new file. Cross-platform
    envelope shape + dispatch isolation + whitelist surface.

Post-Deploy Monitoring & Validation

No additional operational monitoring required: Phase 1 ships with PERCY_IOS_RESOLVER env switch off by default; no production code path changes for any customer until the default is flipped in Phase 4 (separate PR, gated by Phase 0.5 PASS). Phase 4's PR will carry the
operational validation plan.


Compound Engineering
🤖 Generated with Claude Opus 4.7 (1M context, extended thinking) via Claude Code

The Android view-hierarchy resolver is becoming the cross-platform Maestro
resolver (per percy-maestro/docs/plans/2026-04-27-001-feat-ios-element-regions-maestro-hierarchy-plan.md
Unit 1). Rename + shim is purely additive — no behavior change.

- Move src/adb-hierarchy.js → src/maestro-hierarchy.js (git mv preserves history).
- Move test/unit/adb-hierarchy.test.js → test/unit/maestro-hierarchy.test.js.
- Move test/fixtures/adb-hierarchy/ → test/fixtures/maestro-hierarchy/.
- Replace src/adb-hierarchy.js with a 5-line re-export shim. Removed in V1.1
  per the plan's deprecation guidance.
- Update api.js import to ./maestro-hierarchy.js.
- Update logger namespace from core:adb-hierarchy → core:maestro-hierarchy.
- Update file header to reflect cross-platform intent (the file body has been
  maestro-first for some time; the previous file name was always misleading).
- Update test describe block + import + fixture path.

Behavior unchanged. Subsequent units in Phase 1 will add the iOS branch and
api.js dispatch logic; this commit is just the rename so the diffs in those
units stay focused.
…parity

Phase 1 Unit 2a per percy-maestro/docs/plans/2026-04-27-001-feat-ios-element-regions-maestro-hierarchy-plan.md.
Lands the platform-dispatch scaffolding and the cross-platform selector
vocabulary alias. Real iOS resolver implementation deferred to Unit 2b
post Phase 0.5 fixture capture (FIXME-PHASE-0.5 in code).

Platform dispatch:
- dump({ platform }) accepts 'android' (default — backwards compatible) or
  'ios'. iOS branch reads PERCY_IOS_DEVICE_UDID + PERCY_IOS_DRIVER_HOST_PORT
  from env (realmobile-injected per Unit 10a; the wda_port + 2700 formula
  is realmobile-owned per maestro_session.rb:831). Warn-skip with
  reason='env-missing' if either var is unset. Otherwise calls
  runMaestroIosDump which currently returns
  { kind: 'unavailable', reason: 'not-implemented' } as the FIXME-PHASE-0.5
  stub.
- iOS path never invokes adb (verified by test).

R1 vocabulary parity (Android `id` alias):
- flattenMaestroNodes (Android branch) now surfaces resource-id under both
  `resource-id` AND `id` canonical keys on each node. Customer selectors
  `{element: {id: "submit-btn"}}` and `{element: {resource-id: "submit-btn"}}`
  resolve the same node. iOS users writing `{id: ...}` and Android users
  writing the same yaml hit the same code path. Full unified-key migration
  (deprecating `resource-id`) deferred to V1.1.
- SELECTOR_KEYS_UNION = [resource-id, text, content-desc, class, id]
  drives firstMatch validation. ANDROID_SELECTOR_KEYS_WHITELIST and
  IOS_SELECTOR_KEYS_WHITELIST exported separately for callers that want
  per-platform validation.

Tests added:
- Android `id` alias resolves same bbox as `resource-id` (3 tests).
- iOS env-missing path (3 tests covering each env-var combination).
- iOS env-set returns 'not-implemented' (FIXME stub).
- iOS dispatch never invokes adb.
- Default (no platform arg) preserves Android behavior.

Smoke-tested via direct node import; full @percy/core test suite has 27
pre-existing failures in Unit / Install in executable Chromium (unrelated
infrastructure issue), but no regressions in the resolver tests.
Phase 1 Unit 3 per percy-maestro/docs/plans/2026-04-27-001-feat-ios-element-regions-maestro-hierarchy-plan.md.

Wires the maestro-hierarchy resolver into the /percy/maestro-screenshot
relay's iOS element-region dispatch, gated by an env switch so default
(unset) behavior is unchanged. Phase 0.5 empirical probe gates the
default flip to the new path; Phase 4 deletes the legacy iOS branch.

- New: read PERCY_IOS_RESOLVER from process.env. When equal to
  'maestro-hierarchy', iOS element regions flow through the same
  lazy maestroDump({ platform: 'ios' }) + per-region firstMatch
  pattern Android already uses. When unset (or any other value),
  legacy WDA-direct path remains active — no behavior change for
  customers in production today.
- Refactor: the up-front PNG-parse + resolveIosRegions block now only
  fires when the env switch is OFF. With the switch on, that work is
  unnecessary (the resolver is engineered to be lazy + per-region).
- The cross-platform branch in the per-region loop now also covers iOS
  when the switch is on. Same shape as Android: cachedDump lazy memo,
  warn-skip on hierarchy-unavailable, firstMatch + bbox forward on
  success.

Today (env switch unset): only the cross-platform Android path is
exercised. The iOS branch with the switch on is exercised by the
maestro-hierarchy unit tests landed in Unit 2a (which covers the
'env-missing' and 'not-implemented' stub paths). Unit 4 adds the
parity test that exercises both platforms via the same handler.

A real production rollout flips the default to 'maestro-hierarchy'
in Phase 4 (Unit 9) after Phase 0.5 PASSes; until then, keep the
default off.
Phase 1 Unit 4 per percy-maestro/docs/plans/2026-04-27-001-feat-ios-element-regions-maestro-hierarchy-plan.md.

New test file: test/unit/maestro-hierarchy.parity.test.js. Locks in the
contract that both platform branches return the same { kind, ... } envelope,
that the public API surface (SELECTOR_KEYS_WHITELIST + per-platform
whitelists) is consistent, and that platform dispatch isolates the env-var
reads (Android never reads PERCY_IOS_*; iOS never reads ANDROID_SERIAL).

Bug fix discovered during smoke test:
- flattenNodes (the XML/uiautomator code path) was missing the R1 `id`
  alias surface that flattenMaestroNodes (the maestro CLI JSON path)
  already had. So `firstMatch(nodes, { id: 'X' })` worked when nodes came
  from the maestro path but returned null when nodes came from the adb
  fallback path. Now both code paths surface resource-id under both
  `resource-id` and `id` keys consistently.

iOS-side parity assertions in this test are scoped to what Unit 2a's stub
can actually cover — envelope shape, whitelist exports, dispatch isolation.
The Phase 4 follow-up (post Phase 0.5 + Unit 2b) extends this file with
real iOS attribute-mapping assertions backed by a captured iOS hierarchy
fixture.

Smoke-tested via direct node import. The full @percy/core test suite has
27 pre-existing Chromium-installer failures unrelated to this work.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant